package org.ghost.wechat.platform.config;

import com.alibaba.druid.filter.logging.Slf4jLogFilter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.MybatisXMLLanguageDriver;
import com.baomidou.mybatisplus.entity.GlobalConfiguration;
import com.baomidou.mybatisplus.enums.DBType;
import com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.plugins.SqlExplainInterceptor;
import com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.ghost.wechat.platform.common.db.DynamicDataSource;
import org.ghost.wechat.platform.common.enumeration.DbTypeEnum;
import org.ghost.wechat.platform.common.mybatis.MaxRowsLimitInterceptor;
import org.ghost.wechat.platform.config.properties.MasterDbProperties;
import org.ghost.wechat.platform.config.properties.SlaveDbProperties;
import org.ghost.wechat.platform.util.MapResource;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@EnableConfigurationProperties(MybatisProperties.class)
@Configuration
public class DbConfig {
    @Autowired
    private MybatisProperties properties;

    @Autowired
    private MasterDbProperties masterDbProperties;

    @Autowired
    private SlaveDbProperties slaveDbProperties;

    @Bean(value = "statFilter")
    public StatFilter getStatFilter() {
        StatFilter statFilter = new StatFilter();
        statFilter.setSlowSqlMillis(10000);
        statFilter.setLogSlowSql(true);
        return statFilter;
    }

    @Bean(value = "slf4jLogFilter")
    public Slf4jLogFilter getSlf4jLogFilter() {
        Slf4jLogFilter filter = new Slf4jLogFilter();
        filter.setConnectionLogEnabled(false);
        filter.setStatementLogEnabled(true);
        filter.setResultSetLogEnabled(true);
        filter.setStatementExecutableSqlLogEnable(true);
        return filter;
    }

    private DruidDataSource getDruidDataSource(Slf4jLogFilter slf4jLogFilter) throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setFilters("stat");
        if (slf4jLogFilter != null) {
            dataSource.setProxyFilters(Collections.singletonList(slf4jLogFilter));
        }

        return dataSource;
    }

    @Bean(value = "masterDataSource")
    public DruidDataSource getMasterDataSource(Slf4jLogFilter slf4jLogFilter) throws SQLException {
        DruidDataSource dataSource = this.getDruidDataSource(slf4jLogFilter);
        if (masterDbProperties.getMaxActive() != null) {
            dataSource.setMaxActive(masterDbProperties.getMaxActive());
        }
        if (masterDbProperties.getInitialSize() != null) {
            dataSource.setInitialSize(masterDbProperties.getInitialSize());
        }
        if (masterDbProperties.getMaxWait() != null) {
            dataSource.setMaxWait(masterDbProperties.getMaxWait());
        }
        if (masterDbProperties.getMinIdle() != null) {
            dataSource.setMinIdle(masterDbProperties.getMinIdle());
        }
        if (masterDbProperties.getTimeBetweenEvictionRunsMillis() != null) {
            dataSource.setTimeBetweenEvictionRunsMillis(masterDbProperties.getTimeBetweenEvictionRunsMillis());
        }
        if (masterDbProperties.getMinEvictableIdleTimeMillis() != null) {
            dataSource.setMinEvictableIdleTimeMillis(masterDbProperties.getMinEvictableIdleTimeMillis());
        }
        if (StringUtils.isNotBlank(masterDbProperties.getValidationQuery())) {
            dataSource.setValidationQuery(masterDbProperties.getValidationQuery());
        }
        if (masterDbProperties.getTestWhileIdle() != null) {
            dataSource.setTestWhileIdle(masterDbProperties.getTestWhileIdle());
        }
        if (masterDbProperties.getTestOnBorrow() != null) {
            dataSource.setTestOnBorrow(masterDbProperties.getTestOnBorrow());
        }
        if (masterDbProperties.getTestOnReturn() != null) {
            dataSource.setTestOnReturn(masterDbProperties.getTestOnReturn());
        }
        if (masterDbProperties.getPoolPreparedStatements() != null) {
            dataSource.setPoolPreparedStatements(masterDbProperties.getPoolPreparedStatements());
        }
        if (masterDbProperties.getMaxPoolPreparedStatementPerConnectionSize() != null) {
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(masterDbProperties.getMaxPoolPreparedStatementPerConnectionSize());
        }

        dataSource.setDriverClassName(masterDbProperties.getDriverClassName());
        dataSource.setUrl(masterDbProperties.getUrl());
        dataSource.setUsername(masterDbProperties.getUsername());
        dataSource.setPassword(masterDbProperties.getPassword());

        return dataSource;
    }

    @Bean(value = "slaveDataSource")
    public DruidDataSource getSlaveDataSource(Slf4jLogFilter slf4jLogFilter) throws SQLException {
        DruidDataSource dataSource = this.getDruidDataSource(slf4jLogFilter);
        if (slaveDbProperties.getMaxActive() != null) {
            dataSource.setMaxActive(slaveDbProperties.getMaxActive());
        }
        if (slaveDbProperties.getInitialSize() != null) {
            dataSource.setInitialSize(slaveDbProperties.getInitialSize());
        }
        if (slaveDbProperties.getMaxWait() != null) {
            dataSource.setMaxWait(slaveDbProperties.getMaxWait());
        }
        if (slaveDbProperties.getMinIdle() != null) {
            dataSource.setMinIdle(slaveDbProperties.getMinIdle());
        }
        if (slaveDbProperties.getTimeBetweenEvictionRunsMillis() != null) {
            dataSource.setTimeBetweenEvictionRunsMillis(slaveDbProperties.getTimeBetweenEvictionRunsMillis());
        }
        if (slaveDbProperties.getMinEvictableIdleTimeMillis() != null) {
            dataSource.setMinEvictableIdleTimeMillis(slaveDbProperties.getMinEvictableIdleTimeMillis());
        }
        if (StringUtils.isNotBlank(slaveDbProperties.getValidationQuery())) {
            dataSource.setValidationQuery(slaveDbProperties.getValidationQuery());
        }
        if (slaveDbProperties.getTestWhileIdle() != null) {
            dataSource.setTestWhileIdle(slaveDbProperties.getTestWhileIdle());
        }
        if (slaveDbProperties.getTestOnBorrow() != null) {
            dataSource.setTestOnBorrow(slaveDbProperties.getTestOnBorrow());
        }
        if (slaveDbProperties.getTestOnReturn() != null) {
            dataSource.setTestOnReturn(slaveDbProperties.getTestOnReturn());
        }
        if (slaveDbProperties.getPoolPreparedStatements() != null) {
            dataSource.setPoolPreparedStatements(slaveDbProperties.getPoolPreparedStatements());
        }
        if (slaveDbProperties.getMaxPoolPreparedStatementPerConnectionSize() != null) {
            dataSource.setMaxPoolPreparedStatementPerConnectionSize(slaveDbProperties.getMaxPoolPreparedStatementPerConnectionSize());
        }

        dataSource.setDriverClassName(slaveDbProperties.getDriverClassName());
        dataSource.setUrl(slaveDbProperties.getUrl());
        dataSource.setUsername(slaveDbProperties.getUsername());
        dataSource.setPassword(slaveDbProperties.getPassword());

        return dataSource;
    }

    @Bean(value = "dynamicDataSource")
    public DynamicDataSource getDynamicDataSource(DruidDataSource masterDataSource, DruidDataSource slaveDataSource) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(new MapResource<Object, Object>()
                .add(DbTypeEnum.MASTER, masterDataSource)
                .add(DbTypeEnum.SLAVE, slaveDataSource)
                .toMap());

        return dynamicDataSource;
    }

    @ConditionalOnMissingBean
    public NamedParameterJdbcTemplate createNamedParameterJdbcTemplate(DataSource dataSource) {
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean
    public MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean(DataSource dynamicDataSource) {
        MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
        mybatisPlus.setDataSource(dynamicDataSource);
        mybatisPlus.setVfs(SpringBootVFS.class);

        // MP 全局配置，直接使用默认配置(MYSQL，主键使用雪花算法)
        GlobalConfiguration globalConfig = new GlobalConfiguration();
        globalConfig.setDbType(DBType.MYSQL.getDb());
        mybatisPlus.setGlobalConfig(globalConfig);

        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        mybatisConfiguration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        mybatisConfiguration.setMapUnderscoreToCamelCase(true);
        mybatisPlus.setConfiguration(mybatisConfiguration);

        // xmlMapper文件读入
        mybatisPlus.setMapperLocations(this.properties.resolveMapperLocations());
        // 其他属性设置
        mybatisPlus.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        mybatisPlus.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        // 插件设置
        setPlugins(mybatisPlus);

        return mybatisPlus;
    }

    private void setPlugins(MybatisSqlSessionFactoryBean mybatisPlus) {
        //自定义拦截器
        MaxRowsLimitInterceptor maxRowsLimitInterceptor = new MaxRowsLimitInterceptor();
        if (this.properties != null && this.properties.getConfiguration() != null && this.properties.getConfiguration().getVariables() != null) {
            mybatisPlus.setConfigurationProperties(this.properties.getConfiguration().getVariables());
            maxRowsLimitInterceptor.setProperties(this.properties.getConfiguration().getVariables());
        }

        boolean paginationInterceptor = true;
        boolean performanceInterceptor = false;
        boolean optimisticLockerInterceptor = true;
        boolean sqlExplainInterceptor = false;
        if (this.properties != null && this.properties.getConfiguration() != null && this.properties.getConfiguration().getVariables() != null) {
            paginationInterceptor = Boolean.parseBoolean(this.properties.getConfiguration().getVariables().getOrDefault("paginationInterceptor", Boolean.TRUE).toString());
            performanceInterceptor = Boolean.parseBoolean(this.properties.getConfiguration().getVariables().getOrDefault("performanceInterceptor", Boolean.FALSE).toString());
            optimisticLockerInterceptor = Boolean.parseBoolean(this.properties.getConfiguration().getVariables().getOrDefault("optimisticLockerInterceptor", Boolean.TRUE).toString());
            sqlExplainInterceptor = Boolean.parseBoolean(this.properties.getConfiguration().getVariables().getOrDefault("sqlExplainInterceptor", Boolean.FALSE).toString());
        }

        List<Interceptor> interceptorList = new ArrayList<Interceptor>();
        interceptorList.add(maxRowsLimitInterceptor);
        if (paginationInterceptor) {
            interceptorList.add(getPaginationInterceptor());
        }
        if (performanceInterceptor) {
            interceptorList.add(getPerformanceInterceptor());
        }
        if (optimisticLockerInterceptor) {
            interceptorList.add(getOptimisticLockerInterceptor());
        }
        if (sqlExplainInterceptor) {
            interceptorList.add(getSqlExplainInterceptor());
        }
        mybatisPlus.setPlugins(interceptorList.stream().filter(Objects::nonNull).toArray(Interceptor[]::new));
    }

    /**
     * 分页插件
     *
     * @return
     */
    private PaginationInterceptor getPaginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setDialectType("mysql");
        return paginationInterceptor;
    }

    /**
     * 性能分析插件(正式机不要用)
     *
     * @return
     */
    private PerformanceInterceptor getPerformanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setFormat(true);
        performanceInterceptor.setWriteInLog(true);
        performanceInterceptor.setMaxTime(3000L);
        return performanceInterceptor;
    }

    /**
     * 乐观锁插件com.baomidou.mybatisplus.annotations.Version注解
     *
     * @return
     */
    private OptimisticLockerInterceptor getOptimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * 执行计划(正式机不要用)
     *
     * @return
     */
    private SqlExplainInterceptor getSqlExplainInterceptor() {
        SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
        sqlExplainInterceptor.setStopProceed(true);
        return sqlExplainInterceptor;
    }
}
